package com.loong.web.suppert.dao;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import java.util.Properties;

import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.statement.RoutingStatementHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.scripting.defaults.DefaultParameterHandler;
import org.apache.log4j.Logger;

import com.loong.tool.lang.Reflects;
import com.loong.tool.lang.Strings;
import com.loong.web.suppert.collection.param.PageParam;
import com.loong.web.suppert.collection.param.ParamMap;

/**
 * MyBatis分页插件
 *
 * @author 张成轩
 */
@Intercepts(@Signature(method = "prepare", type = StatementHandler.class, args = Connection.class))
public class MPagePlugin implements Interceptor {

	/** 日志输出对象 */
	private static Logger log = Logger.getLogger(MPagePlugin.class);

	/** 日志显示设置 **/
	private boolean showLog = false;

	/**
	 * @param showLog 是否显示日志
	 */
	public void setShowLog(boolean showLog) {

		this.showLog = showLog;
	}

	@Override
	public Object intercept(Invocation invocation) throws Throwable {

		RoutingStatementHandler handler = (RoutingStatementHandler) invocation.getTarget();
		StatementHandler delegate = Reflects.getFieldValue(handler, "delegate");
		// 获取查询对象
		BoundSql bSql = delegate.getBoundSql();
		// 打印日志
		if (showLog) {
			MappedStatement ms = Reflects.getFieldValue(delegate, "mappedStatement");
			StringBuilder debug = new StringBuilder();
			debug.append("[SQL] map: ").append(ms.getId()).append(" sql: ")
					.append(bSql.getSql().replaceAll("[ \r\n\t]+", " ")).append(" param: ")
					.append(bSql.getParameterObject());
			log.info(debug.toString());
		}
		// 获取参数
		Object param = bSql.getParameterObject();
		// 判断参数类型
		if (param instanceof ParamMap) {
			ParamMap p = (ParamMap) param;
			if (p != null) {
				// 获取连接
				Connection connection = (Connection) invocation.getArgs()[0];
				// 获取声明
				MappedStatement statement = Reflects.getFieldValue(delegate, "mappedStatement");
				// 设置总量
				setTotal(statement, connection, p);
				// 重置sql
				Reflects.setFieldValue(bSql, "sql", getLimitSql(bSql.getSql(), p.getPage()));
			}
		}
		return invocation.proceed();
	}

	/**
	 * 设置查询总量
	 * 
	 * @param statement
	 * @param connection
	 * @param param
	 * @throws SQLException
	 */
	private void setTotal(MappedStatement statement, Connection connection, ParamMap param) throws SQLException {

		// 查询对象
		BoundSql bSql = statement.getBoundSql(param);
		// 获取查询参数
		List<ParameterMapping> paramMap = bSql.getParameterMappings();
		// 获取总量查询sql
		String sql = getCountSql(bSql.getSql());
		// 创建总量查询对象
		BoundSql cbSql = new BoundSql(statement.getConfiguration(), sql, paramMap, param);
		// 查询
		ParameterHandler handler = new DefaultParameterHandler(statement, param, cbSql);
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		try {
			// 执行查询
			pstmt = connection.prepareStatement(sql);
			handler.setParameters(pstmt);
			rs = pstmt.executeQuery();
			// 设置总量
			if (rs.next())
				param.getPage().setTotal(rs.getInt(1));
		} catch (SQLException e) {
			throw e;
		} finally {
			if (rs != null)
				try {
					rs.close();
				} catch (SQLException e) {
				}
			if (pstmt != null)
				try {
					pstmt.close();
				} catch (SQLException e) {
				}
		}
	}

	/**
	 * 获取总量查询SQL
	 * 
	 * @param sql SQL
	 * @return 总量查询SQL
	 */
	private String getCountSql(String sql) {

		// 处理大小写
		String upSql = sql.toUpperCase();
		// 起始
		int first = upSql.indexOf("FROM");
		int end;
		// 去除分组及排序
		int group = upSql.lastIndexOf("GROUP");
		int order = upSql.lastIndexOf("ORDER");
		// 终止
		if (group >= 0 && order >= 0)
			end = Math.min(group, order);
		else if (group < 0 && order < 0)
			end = sql.length();
		else
			end = Math.max(group, order);
		// 拼装sql
		return "SELECT COUNT(*) " + sql.substring(first, end);
	}

	/**
	 * 获取分页查询SQL
	 * 
	 * @param sql SQL
	 * @param page 分页参数
	 * @return 分页查询SQL
	 */
	private String getLimitSql(String sql, PageParam page) {

		return Strings.concat(sql, " LIMIT ", page.getStart(), ",", page.getSize());
	}

	@Override
	public Object plugin(Object target) {

		return Plugin.wrap(target, this);
	}

	@Override
	public void setProperties(Properties properties) {

	}
}
